;
; MUS.ASM  Hook the tick vector and use it for 3 channel music
;
; (C) 14/9/88  A.Key
;
;...sincludes:0:
		INCLUDE	INTDOS.INC
;...e
;...sequates:0:
tick		EQU	008H		; slow timer tick interrupt

now_stop	EQU	-1		; duration to stop sound effect
now_rest	EQU	-3		; frequency of rest in data
;...e
;...sdata_seg:0:
data_seg	SEGMENT	'data'

sccs_id		DB	'@(#)Music  12/9/88', 0

counter		DW	0

max_mus		EQU	20		; no. of music channels
max_data	EQU	6000		; no. of duration frequency pairs

mus_fn		DB	'music.ce',0

header		EQU	$
magic		DW	?		; magic number loaded here
n_ch		DW	?		; how many effects
data_size	DW	?		; how many words of data
indexs		DW	max_mus DUP (?)
music_data	DW	2 * max_data DUP (?)

cycles		DW	?		; how many cycles of chord loop
					; could we make in 1/72th sec

ptr_1		DW	?		; pointer to note data
cnt_1		DW	?
counter_1	DW	?		; holds phase of note
pitch_1		DW	?		; set up ready for chord

ptr_2		DW	?		; pointer to note data
cnt_2		DW	?
counter_2	DW	?		; holds phase of note
pitch_2		DW	?		; set up ready for chord

ptr_3		DW	?		; pointer to note data
cnt_3		DW	?
counter_3	DW	?		; holds phase of note
pitch_3		DW	?		; set up ready for chord

base_table	EQU	$

no_of_notes	EQU	7 * 12

;
; Table of frequencys multiplied by 65536
;

SHIFTED		MACRO	value, shift
		DW	value SHL (16-shift)
		DW	value SHR shift
ENDM

IRP	scale,	<6,5,4,3,2,1,0>
		SHIFTED	1760, scale	; A
		SHIFTED	1864, scale	; A#
		SHIFTED	1976, scale	; B
		SHIFTED	2093, scale	; C
		SHIFTED	2217, scale	; C#
		SHIFTED	2349, scale	; D
		SHIFTED	2489, scale	; D#
		SHIFTED	2637, scale	; E
		SHIFTED	2793, scale	; F
		SHIFTED	2959, scale	; F#
		SHIFTED	3135, scale	; G
		SHIFTED	3322, scale	; G#
ENDM

note_table	DW	no_of_notes DUP (?)
					; holds pitch values

data_seg	ENDS
;...e
;...sexterns:0:
		EXTRN	rk_get_joystick:FAR
;...e
;...sMUS_TEXT:0:
MUS_TEXT	SEGMENT PUBLIC 'code'

		ASSUME	CS:MUS_TEXT,DS:data_seg

;...spublics:0:
		PUBLIC	mus_init
		PUBLIC	mus_deinit
		PUBLIC	mus_play
;...e
;...svars:0:
old_ivec	LABEL	DWORD
old_off_ivec	DW	?
old_seg_ivec	DW	?
;...e

;...sBOOLEAN pascal mus_init\40\\41\:0:
;
; BOOLEAN pascal mus_init()
;
mus_init	PROC	FAR

		PUSH	DS
		PUSH	ES
		PUSH	BX
		PUSH	DX
		MOV	AX,SEG data_seg
		MOV	DS,AX
		MOV	ES,AX

		MOV	DX,OFFSET mus_fn
		MOV	AL,0
		DOSCALL	dos_open_fh
		JC	mus_init_error
		PUSH	AX
		MOV	BX,AX
		MOV	CX,3 * 2
		MOV	DX,OFFSET header
		DOSCALL	dos_read_fh
		POP	AX
		PUSH	AX
		MOV	CX,[n_ch]
		ADD	CX,CX		; convert to words
		MOV	DX,OFFSET indexs
		DOSCALL	dos_read_fh
		POP	AX
		PUSH	AX
		MOV	CX,[data_size]	; in words
		ADD	CX,CX
		MOV	DX,OFFSET music_data
		DOSCALL	dos_read_fh
		POP	BX
		DOSCALL	dos_close_fh

		MOV	AX,OFFSET new_isr
		CALL	patch_vec	; patch tick vector
		CALL	time_tick	; count cycles in 1/72th second
		CALL	unpatch_vec	; unpatch tick, dont need it now
		CALL	scale_notes	; correct notes for clock speed
		CALL	scale_data	; translate notes in data

		POP	DX
		POP	BX
		POP	ES
		POP	DS
		MOV	AX,1
		RET

mus_init_error:	POP	DX
		POP	BX
		POP	ES
		POP	DS
		XOR	AX,AX
		RET

mus_init	ENDP
;...e
;...svoid pascal mus_deinit\40\\41\:0:
;
; void pascal mus_deinit()
;
mus_deinit	PROC	FAR

		CALL	music_off
		RET

mus_deinit	ENDP
;...e
;...svoid pascal mus_play\40\ch1\44\ ch2\44\ ch3\41\:0:
;
; BOOLEAN pascal mus_play(ch1, ch2, ch3)
;
mus_play	PROC	FAR

		POP	DX
		POP	SI
		POP	CX		; get ch3
		POP	BX		; get ch2
		POP	AX		; get ch1
		PUSH	SI
		PUSH	DX

		PUSH	DS
		MOV	DX,SEG data_seg
		MOV	DS,DX

		CALL	calc_ptrs	; set up note pointers
		CALL	wait_drive_stop	; ensure drive light off
		MOV	AX,OFFSET null_isr
		CALL	patch_vec	; zap tick
		CALL	music_on	; ensure direct speaker control
		CALL	play_music	; actually output the tune
		CALL	music_off	; ensure all sound killed
		CALL	unpatch_vec	; restore tick
		POP	DS
		RET

mus_play	ENDP
;...e

;...spatch_vec:0:
;
; Patch the interrupt vector with a routine in AX
;
patch_vec	PROC	NEAR

		PUSH	AX
		PUSH	ES
		MOV	AL,tick
		DOSCALL	dos_get_ivec	; returned in ES:BX
		MOV	CS:[old_off_ivec],BX
		MOV	CS:[old_seg_ivec],ES
		POP	ES
		POP	DX

		PUSH	DS
		MOV	AX,SEG MUS_TEXT
		MOV	DS,AX
		MOV	AL,tick
		DOSCALL	dos_set_ivec	; install hacking patch
		POP	DS

		RET

patch_vec	ENDP
;...e
;...sunpatch_vec:0:
;
; Unpatch the interrupt vector
;
unpatch_vec	PROC	NEAR

		PUSH	DS
		MOV	DS,CS:[old_seg_ivec]
		MOV	DX,CS:[old_off_ivec]
		MOV	AL,tick
		DOSCALL	dos_set_ivec	; restore old tick
		POP	DS
		RET

unpatch_vec	ENDP
;...e
;...stime_tick:0:
;
; How many cycles could we make in 1/72th second
;
; Tick rate is 18.2 Hz, so time it and divide by 4
;
time_tick	PROC	NEAR

		MOV	BX,[counter]
tt_2:		CMP	BX,[counter]
		JZ	tt_2		; wait for start of tick
		MOV	BX,[counter]

		XOR	AX,AX
		XOR	DX,DX

tt_4:		ADD	AX,43		;   4 Clocks		1 on 486
		ADC	DX,0		;   4 Clocks		1 on 486

		CMP	BX,[counter]	;  19 Clocks		1/2 on 486
		JZ	tt_4		;  16 Clocks		3 on 486
					;  43 Clocks Total	7 on 486
;
; With original timings DX:AX will be from 222220 - 2222200 for 4 - 40MHz 8086
; With 50MHz 486, DX:AX could get as high as 16876000, since less clocks reqd.
;
		SHR	DX,1
		RCR	AX,1
		SHR	DX,1
		RCR	AX,1
;
; With original timings DX:AX will be from 55555 - 555550 for 4 - 40MHz 8086
; With 50MHz 486, DX:AX could get as high as 4219000.
;
					; DX:AX will be in the range
					; 55555 - 555550 for 4 - 40MHz 8086
		MOV	BX,218		; Clocks in a chord loop ###
					; On a 486 this is about 98
		DIV	BX
;
; With original timings AX will be in the range 255 - 2548
; With 50MHz 486, AX could go as high as 19353
;
		MOV	[cycles],AX
		RET

time_tick	ENDP
;...e
;...sscale_notes:0:
;
; Create note_table from base_table allowing for clock speed
;
; On entry we know how many cycles we could make in 1/72th sec
;
; pitch * cycles
; -------------- = freq / 72
;     65536
;
; Also note that :-
;
; loop_time = 1 / 72
;             ------
;             cycles
;
; Therefore :-
;
; pitch = 65536 * freq
;         ------------
;         72 * cycles
;
scale_notes	PROC	NEAR

		PUSH	CX
		PUSH	SI
		PUSH	DI

		MOV	AX,[cycles]	; BX will be in the range 255 - 2548
		MOV	BX,18
		MUL	BX		; range 4587 - 45871
					; for machines of 4 - 40MHz 8086

	REPT 4
	SHR DX,1
	RCR AX,1
	ENDM

		MOV	BX,AX
		MOV	SI,OFFSET base_table
		MOV	DI,OFFSET note_table
		MOV	CX,no_of_notes
sn_2:		LODSW
		MOV	DX,[SI]
		ADD	SI,2		; DX:AX = frequency * 65536
					; DX:AX will be in the range
					; 27 * 65536 - 3322 * 65536

	REPT 4
	SHR DX,1
	RCR AX,1
	ENDM

		DIV	BX		; because lowest value of BX greater
					; than highest value of DX know
					; DX is now 0 (and no /0 int occurs)
		SHR	AX,1		; divided by thirty six
		SHR	AX,1		; divided by seventy two

		STOSW

		LOOP	sn_2

		POP	DI
		POP	SI
		POP	CX

		RET

scale_notes	ENDP
;...e
;...sscale_data:0:
;
; For each note in the music_data convert its note number to a pitch value
;
scale_data	PROC	NEAR

		PUSH	CX
		PUSH	DI

		MOV	DI,OFFSET music_data
		MOV	CX,[data_size]	; in words
		SHR	CX,1		; since data is in duration freq pairs
sd_loop:	ADD	DI,2		; skip duration
		MOV	BX,[DI]		; BX = note number
		CMP	BX,now_rest
		JZ	sd_rest
		SHL	BX,1
		ADD	BX,OFFSET note_table
		MOV	AX,[BX]		; extract pitch value
		JMP	SHORT sd_store
sd_rest:	XOR	AX,AX		; rest has a zero pitch value
sd_store:	STOSW			; store in data
		LOOP	sd_loop

		POP	DI
		POP	CX

		RET

scale_data	ENDP
;...e

;...scalc_ptrs:0:
;
; Calculate pointers for the 3 channels
;
; Input:	AX,BX,CX = channels nos. for the channels
;
calc_ptrs	PROC	NEAR

		MOV	SI,AX
		CALL	calc_ptr
		MOV	[ptr_1],SI

		MOV	SI,BX
		CALL	calc_ptr
		MOV	[ptr_2],SI

		MOV	SI,CX
		CALL	calc_ptr
		MOV	[ptr_3],SI

		RET

calc_ptrs	ENDP
;
;
calc_ptr	PROC	NEAR

		ADD	SI,SI
		ADD	SI,OFFSET indexs
		MOV	SI,[SI]
		ADD	SI,SI
		ADD	SI,OFFSET music_data
		RET

calc_ptr	ENDP
;...e
;...swait_drive_stop:0:
;
; Wait for the disc light to go out
;
wait_drive_stop	PROC	NEAR

		PUSH	DS
		XOR	AX,AX
		MOV	DS,AX		; index low memory
		MOV	SI,0440H	; address of disc revolution counter
		CLI
		CMP	BYTE PTR [SI],0	; is it already 0
		JNZ	wds_still_on
		STI
		POP	DS
		RET

wds_still_on:	MOV	BYTE PTR [SI],1	; make it nearly 0
		STI
wds_wait_off:	CMP	BYTE PTR [SI],0	; down to 0 yet
		JNZ	wds_wait_off	; no, so keep waiting
		POP	DS
		RET

wait_drive_stop	ENDP
;...e
;...smusic_on:0:
;
; Ensure PIT is permanently on, so we can do direct speaker control
;
music_on	PROC	NEAR

		IN	AL,061H
		AND	AL,0FEH		; reset bit 0
		OUT	061H,AL
		RET

music_on	ENDP
;...e
;...splay_music:0:
;
; Play the music
;
play_music	PROC	NEAR

		XOR	AX,AX

		MOV	[cnt_1],AX
		MOV	[cnt_2],AX
		MOV	[cnt_3],AX	; ensure new notes immediatley

mp_0:
		CALL	rk_get_joystick	; has a key been pressed
		AND	AL,AL		; if it has, a bit will be set here
		JZ	mp_2

mp_done:
		RET			; return, all done

mp_2:
		CMP	[cnt_1],0	; any more of current note to play ?
		JNE	mp_4		; if so then skip this bit
		MOV	SI,[ptr_1]	; get pointer
		LODSW			; get new count
		CMP	AX,now_stop	; end of tune
		JE	mp_done		; if so, go to end stuff
		MOV	[cnt_1],AX	; store locally
		LODSW			; get new pitch value
		MOV	[pitch_1],AX
		MOV	[ptr_1],SI	; keep advanced pointer
mp_4:

		CMP	[cnt_2],0	; any more of current note to play ?
		JNE	mp_6		; if so then skip this bit
		MOV	SI,[ptr_2]	; get pointer
		LODSW			; get new count
		MOV	[cnt_2],AX	; store locally
		LODSW			; get new pitch value
		MOV	[pitch_2],AX
		MOV	[ptr_2],SI	; keep advanced pointer
mp_6:

		CMP	[cnt_3],0	; any more of current note to play ?
		JNE	mp_8		; if so then skip this bit
		MOV	SI,[ptr_3]	; get pointer
		LODSW			; get new count
		MOV	[cnt_3],AX	; store locally
		LODSW			; get new pitch value
		MOV	[pitch_3],AX
		MOV	[ptr_3],SI	; keep advanced pointer
mp_8:

;
; At this stage the pitch values are set up and
; the cnt values hold how much to do of each note
;

;
; Find longest common note and do this much
;

		MOV	DX,[cnt_1]
		CMP	DX,[cnt_2]
		JBE	mp_10
		MOV	DX,[cnt_2]
mp_10:		CMP	DX,[cnt_3]
		JBE	mp_12
		MOV	DX,[cnt_3]
mp_12:

		MOV	CX,DX		; how many semi-quavers we
					; can do together

mp_sq_loop:	PUSH	CX

		MOV	CX,[cycles]
		SHL	CX,1
		ADD	CX,[cycles]	; work in units of 3/72th sec

mp_chord_loop:
		MOV	BX,[counter_3]	;  14 Clocks		1 on 486
		ADD	BX,[pitch_3]	;  19 Clocks		1 on 486
		MOV	[counter_3],BX	;  14 Clocks		1 on 486
		ROL	BX,1		;   2 Clocks		1 on 486
		MOV	AL,010010B	;   4 Clocks		1 on 486
		RCL	AL,1		;   2 Clocks		1 on 486
		RCL	AL,1		;   2 Clocks		1 on 486
		OUT	61H,AL		;  10 Clocks		~20? on 486
					; channel out total
					;  67 Clocks		27 on 486

		MOV	BX,[counter_2]
		ADD	BX,[pitch_2]
		MOV	[counter_2],BX
		ROL	BX,1
		MOV	AL,010010B
		RCL	AL,1
		RCL	AL,1
		OUT	61H,AL

		MOV	BX,[counter_1]
		ADD	BX,[pitch_1]
		MOV	[counter_1],BX
		ROL	BX,1
		MOV	AL,010010B
		RCL	AL,1
		RCL	AL,1
		OUT	61H,AL

		LOOP	mp_chord_loop	;  17 Cycles

					; 218 Clocks = whole loop (on a 8086)

					; if this changes search for ### and
					; alter the constant used there too

		POP	CX

		LOOP	mp_sq_loop

		SUB	[cnt_1],DX
		SUB	[cnt_2],DX
		SUB	[cnt_3],DX	; remember played some of note

		JMP	mp_0		; now do next notes

play_music	ENDP
;...e
;...smusic_off:0:
;
; Turn off the sound chip
;
music_off	PROC	NEAR

		IN	AL,061H
		AND	AL,0FCH		; turn off 8253 and AND gate
		OUT	061H,AL
		RET

music_off	ENDP
;...e

;...snew_isr:0:
;
; Called from tick interrupt vector at each unit of time
;
; IF = 0 at this point
;
new_isr		PROC	FAR

		PUSH	DS
		PUSH	AX
		MOV	AX,SEG data_seg
		MOV	DS,AX
		INC	[counter]
		POP	AX
		POP	DS
		JMP	CS:[old_ivec]

new_isr		ENDP
;...e
;...snull_isr:0:
;
; Do nothing and return
;
null_isr	PROC	FAR

		PUSH	AX
		MOV	AL,020H
		OUT	020H,AL
		POP	AX
		IRET

null_isr	ENDP
;...e

MUS_TEXT	ENDS
;...e
END
